﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Management;
using System.Runtime.InteropServices;

namespace Red.Launcher.Utilities
{
    public static class ProcessExtension
    {
        #region PInvoke

        [DllImport( "user32.dll" )]
        static extern bool SetForegroundWindow( IntPtr hWnd );

        /// <summary>
        /// A utility class to determine a process parent.
        /// </summary>
        [StructLayout( LayoutKind.Sequential )]
        public struct ParentProcessUtilities
        {
            // These members must match PROCESS_BASIC_INFORMATION
            internal IntPtr Reserved1;
            internal IntPtr PebBaseAddress;
            internal IntPtr Reserved2_0;
            internal IntPtr Reserved2_1;
            internal IntPtr UniqueProcessId;
            internal IntPtr InheritedFromUniqueProcessId;
        }

        [DllImport( "ntdll.dll" )]
        private static extern int NtQueryInformationProcess( IntPtr processHandle, int processInformationClass, ref ParentProcessUtilities processInformation, int processInformationLength, out int returnLength );

        #endregion PInvoke

        #region Extension Methods

        public static bool BringToForeground( this Process process )
        {
            return SetForegroundWindow( process.MainWindowHandle );
        }

        /// <summary>
        /// Gets the parent process of the current process.
        /// </summary>
        /// <returns>An instance of the Process class.</returns>
        /// <remarks>http://stackoverflow.com/questions/394816/how-to-get-parent-process-in-net-in-managed-way</remarks>
        public static Process GetParentProcess( this Process process )
        {
            string query = $"SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {process.Id}";
            ManagementObjectSearcher search = new ManagementObjectSearcher( "root\\CIMV2", query );

            var results = search.Get().GetEnumerator();
            results.MoveNext();

            uint id = (uint)results.Current[ "ParentProcessId" ];

            try
            {
                return Process.GetProcessById( (int)id );
            }
            catch( ArgumentException )
            {
                // No process with specified id
                return null;
            }
        }

        public static IEnumerable<Process> GetChildProcesses( this Process process )
        {
            List<Process> children = new List<Process>();
            ManagementObjectSearcher mos = new ManagementObjectSearcher( $"Select * From Win32_Process Where ParentProcessID={process.Id}" );

            foreach ( ManagementObject mo in mos.Get() )
            {
                children.Add( Process.GetProcessById( Convert.ToInt32( mo[ "ProcessID" ] ) ) );
            }

            return children;
        }

        #endregion Extension Methods

        #region Private Methods

        /// <summary>
        /// Gets the parent process of specified process.
        /// </summary>
        /// <param name="id">The process id.</param>
        /// <returns>An instance of the Process class.</returns>
        private static Process GetParentProcess( int id )
        {
            Process process = Process.GetProcessById( id );
            return GetParentProcess( process.Handle );
        }

        /// <summary>
        /// Gets the parent process of a specified process.
        /// </summary>
        /// <param name="handle">The process handle.</param>
        /// <returns>An instance of the Process class or null if an error occurred.</returns>
        private static Process GetParentProcess( IntPtr handle )
        {
            ParentProcessUtilities pbi = new ParentProcessUtilities();

            int returnLength;
            int status = NtQueryInformationProcess( handle, 0, ref pbi, Marshal.SizeOf( pbi ), out returnLength );
            if ( status != 0 )
            {
                return null;
            }

            try
            {
                return Process.GetProcessById( pbi.InheritedFromUniqueProcessId.ToInt32() );
            }
            catch ( ArgumentException )
            {
                // not found
                return null;
            }

            #endregion Private Methods
        }
    }
}
